package com.tanhua.management.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.management.mapper.AdminMapper;
import com.tanhua.management.pojo.Admin;
import com.tanhua.management.util.UserThreadLocal;
import com.tanhua.management.util.VerifyCodeUtil;
import com.tanhua.management.vo.AdminVo;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class LoginService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private AdminMapper adminMapper;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * @param uuid
     * @param response
     * @return
     */
    public boolean CreateVerification(String uuid, HttpServletResponse response) {
        try {
            //这是可以将文件写入到内存中的流
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            //调用util中的方法得到对应的验证码
            String code = VerifyCodeUtil.drawImage(output);
            System.out.println("图形验证码为:" + code);
            //将这个code 验证码保存在 redis中,其key的唯一性就是要传过来的UUID
            String redisKey = "LOGIN_CODE_" + uuid;
            //设置五分钟过期
            this.redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(5));
            //这是专门在servlet中传递的流
            ServletOutputStream out = response.getOutputStream();
            /**
             * 将内存流交给 servlet 流
             */
            output.writeTo(out);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 登录功能的实现
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     uuid
     * @return
     */
    public String login(String username, String password, String code, String uuid) {
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        if (StringUtils.isEmpty(password)) {
            return null;
        }
        //还要再根据这个 uuid 去redis中查找有没有这个 key ,辨别整个验证码是否正确
        String redisKey = "LOGIN_CODE_" + uuid;
        String value = this.redisTemplate.opsForValue().get(redisKey);
        //如果验证码不一样,直接返回一个false
        if (!StringUtils.equalsIgnoreCase(value, code)) {
            return null;
        }
        //通过username的唯一性来得到这个唯一的数据
        //先给这个密码加密
        password = DigestUtils.md5Hex(password);
        System.out.println(password);
        QueryWrapper<Admin> wrapper = new QueryWrapper<>();
        //查找数据库有没有这个用户
        wrapper.eq("username", username)
                .eq("password", password);
        Admin admin = this.adminMapper.selectOne(wrapper);

        if (admin == null) {
            return null;
        }
        //有值且输入正确
        //生成 token
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("id", admin.getId());
        claims.put("username", admin.getUsername());

        // 生成token
        String token = Jwts.builder()
                .setClaims(claims) //payload，存放数据的位置，不能放置敏感数据，如：密码等
                .signWith(SignatureAlgorithm.HS256, this.secret) //设置加密方法和加密盐
                .setExpiration(new DateTime().plusHours(12).toDate()) //设置过期时间，12小时后过期
                .compact();
        return token;
    }


    /**
     * 检验 token
     *
     * @param token
     * @return
     */
    public AdminVo queryAdminByToken(String token) {
        AdminVo adminVo = UserThreadLocal.get();
        if (adminVo!=null){
            System.out.println(adminVo);
        }
        System.out.println(token);
        try {
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(this.secret)
                    .parseClaimsJws(token)
                    .getBody();
            System.out.println(body); //含有当初 用来加密的 admin 数据

            /**
             * 通过唯一标识 id 来得到 对应的 admin
             */
            Long id = Long.valueOf(body.get("id").toString());
            QueryWrapper<Admin> wrapper = new QueryWrapper<>();
            wrapper.eq("id",id);
            Admin one = this.adminMapper.selectOne(wrapper);
            AdminVo adminvo = new AdminVo();
            String redisKey = "TANHUA_TOKEN_ADMIN_" + token;
           //判断在 redis 中是否含有 这个 缓存的 redisKey
            if (this.redisTemplate.hasKey(redisKey)) {
                //如果有,得到 redis 中存入的手机号,并将其赋值给 user 对象,
                String value = this.redisTemplate.opsForValue().get(redisKey);
                adminvo = MAPPER.readValue(value, AdminVo.class);
                adminvo.setAvatar(one.getAvatar());
                return adminvo;
                //在这儿要设置值
            } else {
                if (one == null) {
                    return null;
                }

                adminvo.setUid(one.getId().toString());
                adminvo.setUsername(one.getUsername());
                //设置 存活时间
                //jwt 中的 为 秒 *1000 变毫秒 减去当前时间的毫秒值 即为存活时间
                long timeout = Long.valueOf(body.get("exp").toString()) * 1000 - System.currentTimeMillis();
                this.redisTemplate.opsForValue().set(redisKey,MAPPER.writeValueAsString(adminvo), timeout, TimeUnit.MILLISECONDS);

            }
            adminvo.setAvatar(one.getAvatar());
            return adminvo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 登出就是将这个 token 变为无效,直接删除这个 token对应的信息
     * @param token
     * @return
     */
    public boolean logout(String token) {
        try {
            //将这个token 的存活时间清零
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(this.secret)
                    .parseClaimsJws(token)
                    .getBody();
            body.replace("exp",0);
            System.out.println(body.toString());
            String redisKey = "TANHUA_TOKEN_ADMIN_" + token;
            this.redisTemplate.delete(redisKey);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
